﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Xml.Linq;
using umbraco.IO;
using umbraco;
using System.Security.Cryptography;
using umbraco.cms.businesslogic.media;
using umbraco.DataLayer;
using umbraco.interfaces;
using umbraco.cms.businesslogic.datatype.controls;
using System.Xml.XPath;
using umbraco.BusinessLogic;

namespace DigibizTree
{
    public class DigibizMediaHelper
    {
        public static IDataType uploadField = new Factory().GetNewObject(new Guid("5032a6e6-69e3-491d-bb28-cd31cd11086c"));

        /// <summary>
        /// Gets the SqlHelper used by Umbraco
        /// </summary>
        public static ISqlHelper SqlHelper
        {
            get { return umbraco.BusinessLogic.Application.SqlHelper; }
        }

        #region Exceptions

        /// <summary>
        /// Log an exception.
        /// </summary>
        /// <param name="ex"></param>
        public static void LogException(Exception ex)
        {
            LogException(ex, null);
        }

        /// <summary>
        /// Log an exception.
        /// </summary>
        /// <param name="ex"></param>
        /// <param name="nodeId"></param>
        /// <param name="type"></param>
        public static void LogException(Exception ex, int? nodeId)
        {
            try
            {
                if (nodeId == null)
                {
                    nodeId = -1;
                }

                StringBuilder comment = new StringBuilder();
                comment.Append("Exception: " + ex.Message);
                if (!string.IsNullOrEmpty(ex.StackTrace))
                {
                    comment.Append(" - StackTrace: " + ex.StackTrace);
                }

                if (ex.InnerException != null)
                {
                    comment.Append(" - InnerException: " + ex.InnerException.Message);
                    if (!string.IsNullOrEmpty(ex.InnerException.StackTrace))
                    {
                        comment.Append(" - InnerStackTrace: " + ex.InnerException.StackTrace);
                    }
                }

                Log.Add(LogTypes.Error, (int)nodeId, comment.ToString());
            }
            catch (Exception newEx)
            {
                //Do nothing because nothing can be done with this exception.
            }
        }

        #endregion

        #region Data / Media Property Helpers

        /// <summary>
        /// Get the selected id's from the hidden fiels and return them as the full media xml.
        /// </summary>
        /// <returns></returns>
        public static XDocument GetXML(string[] ids)
        {
            if (ids.Any())
            {
                return new XDocument(
                        new XElement("DAMP",
                            new XAttribute("fullMedia", string.Empty),
                            from id in ids
                            select GetMediaElements(id)
                            )
                        );
            }

            return null;
        }

        /// <summary>
        /// Check if the media type has an upload datatype.
        /// </summary>
        /// <param name="mediaTypeId"></param>
        /// <returns></returns>
        public static bool HasUploadDataType(int mediaTypeId)
        {
            //This query will check if the media type has the upload datatype.
            string sql = string.Format(@"
                        select count(*) from cmsPropertyType cpt
                            inner join cmsDataType cdt on cpt.dataTypeId = cdt.nodeId
                        where cpt.contentTypeId = {0}
                        and cdt.controlId = '{1}'", mediaTypeId, uploadField.Id);

            //Execute the query and get the count.
            int count = SqlHelper.ExecuteScalar<int>(sql);

            //If the count is bigger than 1 at least 1 upload datatype has been found.
            return count > 0;
        }

        /// <summary>
        /// Return the media item of the passed id as an XElement.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public static XElement GetMediaElements(string id)
        {
            try
            {
                if (MediaExists(id))
                {
                    //Get the xml from the cmsContentXml table. We don't use library.GetMedia here because of some cache problems and because it doesn't support CDATA.
                    XElement xml = XElement.Load(SqlHelper.ExecuteXmlReader("select xml from cmsContentXml where nodeId = " + id));

                    return new XElement("mediaItem", xml);
                }
            }
            catch (Exception ex)
            {
                LogException(ex);
            }

            //If the media item doesn't exist anymore don't return a mediaItem xml element.
            return null;
        }

        /// <summary>
        /// Checks if the media items exists by looking for it in the cmsContentXml table. If it's not found it's logged.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public static bool MediaExists(string id)
        {
            try
            {
                //Call this method to genereate the media xml. See http://issues.umbraco.org/issue/U4-1772
                library.GetMedia(Convert.ToInt32(id), false);
            }
            catch (Exception ex)
            {
            }

            bool exists = SqlHelper.ExecuteScalar<int>("select count(*) from cmsContentXml where nodeId = " + id) == 1;
            if (!exists)
            {
                Log.Add(LogTypes.Custom, -1, string.Format("No media with id {0} was found. DAMP skipped the media item.", id));
            }
            return exists;
        }

        /// <summary>
        /// Return the media id from an element.
        /// </summary>
        /// <param name="mediaElement"></param>
        /// <returns></returns>
        public static int GetMediaId(XElement mediaElement)
        {
            XAttribute idAttribute = mediaElement.Attribute("id");

            if (idAttribute == null)
            {
                return -1;
            }

            return Convert.ToInt32(idAttribute.Value);
        }

        /// <summary>
        /// Return the media name of an element.
        /// </summary>
        /// <param name="mediaElement"></param>
        /// <returns></returns>
        public static string GetMediaName(XElement mediaElement)
        {
            XAttribute nodeNameAttribute = mediaElement.Attribute("nodeName");

            if (nodeNameAttribute == null)
            {
                return string.Empty;
            }

            return nodeNameAttribute.Value;
        }

        /// <summary>
        /// Return the media type of an element.
        /// </summary>
        /// <param name="mediaElement"></param>
        /// <returns></returns>
        public static string GetMediaType(XElement mediaElement)
        {
            XAttribute nodeTypeAliasAttribute = mediaElement.Attribute("nodeTypeAlias");

            if (nodeTypeAliasAttribute == null)
            {
                return string.Empty;
            }

            return nodeTypeAliasAttribute.Value;
        }

        /// <summary>
        /// Return the media link of an element.
        /// </summary>
        /// <param name="mediaElement"></param>
        /// <returns></returns>
        public static string GetMediaLink(XElement mediaElement)
        {
            XElement filePathElement = DigibizMediaHelper.GetFilePathElement(mediaElement);

            if (filePathElement == null)
            {
                return "#";
            }

            return filePathElement.Value;
        }

        /// <summary>
        /// Returns the URL to the cropped version of a media element, under the specified Property & Crop Name if it exists.  
        /// If no Crop Name specified, returns the first crop available.
        /// </summary>
        /// <param name="mediaElement"></param>
        /// <param name="cropPropertyAlias"></param>
        /// <param name="cropName"></param>
        /// <returns></returns>
        public static string GetMediaCrop(XElement mediaElement, string cropPropertyAlias, string cropName)
        {
            // Wrapping entire block in a try - invalid cropPropertyAlias causes exception and prevents selected media from loading
            try
            {
                if (!string.IsNullOrEmpty(cropPropertyAlias) && mediaElement.Elements(cropPropertyAlias).Count() > 0)
                {
                    XElement cropPropertyElement = mediaElement.Elements(cropPropertyAlias).FirstOrDefault();
                    if (cropPropertyElement.Descendants("crops").Count() > 0)
                    {
                        XElement cropsContainerElement = cropPropertyElement.Descendants("crops").FirstOrDefault();
                        XElement cropDataElement;
                        if (string.IsNullOrEmpty(cropName))
                        {
                            // Return the first crop found
                            cropDataElement = cropsContainerElement.Descendants().Where(x => x.Name == "crop").FirstOrDefault();
                        }
                        else if (cropsContainerElement.Descendants().Where(x => x.Attribute("name").Value == cropName && x.Name == "crop").Count() > 0)
                        {
                            cropDataElement = cropsContainerElement.Descendants().Where(x => x.Attribute("name").Value == cropName && x.Name == "crop").FirstOrDefault();
                        }
                        else 
                        {
                            // If we specified a crop name and it wasn't found, don't return any crops
                            return null;
                        }
                        return cropDataElement.Attribute("url").Value;
                    }
                }
            }
            catch (Exception ex)
            {
                // Log?
                return null;
            }
            return null;
        }

        /// <summary>
        /// Return the media icon/image of an element.
        /// </summary>
        /// <param name="mediaElement"></param>
        /// <returns></returns>
        public static string GetMediaIcon(XElement mediaElement)
        {
            string umbPath = IOHelper.ResolveUrl(SystemDirectories.Umbraco);

            string mediaType = DigibizMediaHelper.GetMediaType(mediaElement);

            XElement filePathElement = DigibizMediaHelper.GetFilePathElement(mediaElement);

            // If there is no "file" on this media, return the media type's thumbnail instead
            if (filePathElement == null)
            {
                MediaType mt = MediaType.GetByAlias(mediaType);
                return umbPath + "/images/thumbnails/" + mt.Thumbnail;
            }

            string filePath = filePathElement.Value;
            string ext = filePath.Substring(filePath.LastIndexOf(".") + 1);
            string extLow = ext.ToLower();

            //If the extension isn't part of the imageTypes select a built-in icon.
            if (DigibizConstants.IMAGETYPES.IndexOf("," + extLow + ",") < 0)
            {
                switch (extLow)
                {
                    case "docx":
                    case "doc":
                        return umbPath + "/plugins/DigibizAdvancedMediaPicker/Doc_icon.png";
                    case "xlsx":
                    case "xls":
                    case "csv":
                        return umbPath + "/plugins/DigibizAdvancedMediaPicker/Excel_icon.png";
                    case "pptx":
                    case "ppsx":
                    case "ppt":
                    case "pps":
                        return umbPath + "/plugins/DigibizAdvancedMediaPicker/Power_icon.png";
                    case "pdf":
                        return umbPath + "/plugins/DigibizAdvancedMediaPicker/Pdf_icon.png";
                    case "txt":
                        return umbPath + "/plugins/DigibizAdvancedMediaPicker/Text_icon.png";
                    case "aac":
                    case "aif":
                    case "iff":
                    case "m3u":
                    case "mid":
                    case "mp3":
                    case "mpa":
                    case "ra":
                    case "wav":
                    case "wma":
                        return umbPath + "/plugins/DigibizAdvancedMediaPicker/Audio_icon.png";
                    case "3g2":
                    case "3gp":
                    case "asf":
                    case "asx":
                    case "avi":
                    case "flv":
                    case "mov":
                    case "mp4":
                    case "mpg":
                    case "rm":
                    case "swf":
                    case "vob":
                    case "wmv":
                        return umbPath + "/plugins/DigibizAdvancedMediaPicker/Video_icon.png";
                    case "7z":
                    case "deb":
                    case "gz":
                    case "pkg":
                    case "rar":
                    case "sit":
                    case "sitx":
                    case "zip":
                    case "zipx":
                        return umbPath + "/plugins/DigibizAdvancedMediaPicker/Zip_icon.png";
                    default:
                        MediaType mt = MediaType.GetByAlias(mediaType);
                        return umbPath + "/images/thumbnails/" + mt.Thumbnail;
                }
            }


            //If the extension is part of the imageTypes return the thumbnail.  Check for .png thumbnail before returning the normal .jpg version
            if (System.IO.File.Exists(HttpContext.Current.Server.MapPath(filePath.Replace("." + ext, "_thumb.png"))))
                return filePath.Replace("." + ext, "_thumb.png");
            else
                return filePath.Replace("." + ext, "_thumb.jpg");
        }

        /// <summary>
        /// Get the file path element. If the old xml schema is used we need to fetch this a different way.
        /// </summary>
        /// <param name="mediaElement"></param>
        /// <returns></returns>
        public static XElement GetFilePathElement(XElement mediaElement)
        {
            XElement filePathElement =
                !UmbracoSettings.UseLegacyXmlSchema
                ?
                mediaElement.Element("umbracoFile")
                :
                mediaElement.Elements("data").Where(x => x.Attribute("alias").Value == "umbracoFile").FirstOrDefault()
                ;

            if (filePathElement == null)
            {
                //If the filepath is not found try another option.
                filePathElement =
                    !UmbracoSettings.UseLegacyXmlSchema
                    ?
                    mediaElement.Element("fileName")
                    :
                    mediaElement.Elements("data").Where(x => x.Attribute("alias").Value == "fileName").FirstOrDefault()
                    ;
            }

            if (filePathElement == null)
            {
                //If the filepath is still not found try a third option.
                filePathElement =
                    !UmbracoSettings.UseLegacyXmlSchema
                    ?
                    mediaElement.Element("file")
                    :
                    mediaElement.Elements("data").Where(x => x.Attribute("alias").Value == "file").FirstOrDefault()
                    ;
            }

            return filePathElement;
        }

        public static string GetMediaMd5Sum(XElement mediaElement)
        {
            // get string of unique media va.lues
            string plaintekst = mediaElement.Attribute("version").Value + mediaElement.Attribute("updateDate").Value + mediaElement.Attribute("path").Value;

            // First we need to convert the string into bytes, which
            // means using a text encoder.
            Encoder enc = System.Text.Encoding.Unicode.GetEncoder();

            // Create a buffer large enough to hold the string
            byte[] unicodeText = new byte[plaintekst.Length * 2];
            enc.GetBytes(plaintekst.ToCharArray(), 0, plaintekst.Length, unicodeText, 0, true);

            // Now that we have a byte array we can ask the CSP to hash it
            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] result = md5.ComputeHash(unicodeText);

            // Build the final string by converting each byte
            // into hex and appending it to a StringBuilder
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < result.Length; i++)
            {
                sb.Append(result[i].ToString("X2"));
            }

            // And return it
            return sb.ToString();
        }

        #endregion

        #region Validation/Restriction Helpers

        /// <summary>
        /// Determines whether a media node is a folder (if it allows any child nodes)
        /// </summary>
        /// <param name="mediaElement"></param>
        /// <returns></returns>
        public static bool IsFolder(XElement mediaElement)
        {
            umbraco.cms.businesslogic.media.Media m = new umbraco.cms.businesslogic.media.Media(Convert.ToInt32(mediaElement.Attribute("id").Value));
            if (m != null)
            {
                if (m.ContentType.AllowedChildContentTypeIDs.Count() > 0)
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Determines whether a media node allows the specified child type to be created beneath it
        /// </summary>
        /// <param name="mediaElement"></param>
        /// <param name="selectedMediaType"></param>
        /// <returns></returns>
        public static bool IsChildMediaTypeAllowed(XElement mediaElement, int selectedMediaType)
        {
            umbraco.cms.businesslogic.media.Media m = new umbraco.cms.businesslogic.media.Media(Convert.ToInt32(mediaElement.Attribute("id").Value));
            if (m != null)
            {
                if (m.ContentType.AllowedChildContentTypeIDs.Where(n => n == selectedMediaType).Count() > 0)
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Check if the extension of the media link if part of the allowed extensions.
        /// </summary>
        /// <param name="mediaElement"></param>
        /// <param name="allowedExtensions"></param>
        /// <returns></returns>
        public static bool HasAllowedExtension(XElement mediaElement, string allowedExtensions)
        {
            if (string.IsNullOrEmpty(allowedExtensions) || allowedExtensions == DigibizConstants.ALLEXTENSIONS)
            {
                return true;
            }

            XElement filePathElement = DigibizMediaHelper.GetFilePathElement(mediaElement);

            if(filePathElement == null)
            {
                //If no file path is found no extension will match so we can return false.
                return false;
            }

            if (!allowedExtensions.StartsWith(","))
            {
                allowedExtensions = "," + allowedExtensions;
            }

            if (!allowedExtensions.EndsWith(","))
            {
                allowedExtensions = allowedExtensions + ",";
            }

            string filePath = filePathElement.Value;
            string extLow = filePath.Substring(filePath.LastIndexOf(".") + 1).ToLower();

            return !(allowedExtensions.IndexOf("," + extLow + ",") < 0);
        }

        /// <summary>
        /// Check if the media type of the passed mediaElement is allowed
        /// </summary>
        /// <param name="mediaElement"></param>
        /// <param name="allowedExtensions"></param>
        /// <returns></returns>
        public static bool HasAllowedMediaType(XElement mediaElement, string allowedMediaTypes)
        {
            if (string.IsNullOrEmpty(allowedMediaTypes))
            {
                return true;
            }

            string mediaType = mediaElement.Attribute("nodeType").Value;

            if (mediaType == null)
            {
                return false;
            }

            if (!allowedMediaTypes.StartsWith(","))
            {
                allowedMediaTypes = "," + allowedMediaTypes;
            }

            if (!allowedMediaTypes.EndsWith(","))
            {
                allowedMediaTypes = allowedMediaTypes + ",";
            }


            return !(allowedMediaTypes.IndexOf("," + mediaType + ",") < 0);
        }

        #endregion

        #region Allowed Messages

        /// <summary>
        /// Return a message which extensions are allowed in the correct format.
        /// </summary>
        /// <param name="allowedExtension"></param>
        /// <returns></returns>
        public static string GetAllowedMessageExtensions(string allowedExtension)
        {
            if (string.IsNullOrWhiteSpace(allowedExtension))
            {
                return "all";
            }
            return allowedExtension.Replace(" ", string.Empty).TrimStart(',').TrimEnd(',').Replace(",", " | ");
        }

        /// <summary>
        /// Return a message which media types are allowed in the correct format.
        /// </summary>
        /// <param name="allowedExtension"></param>
        /// <returns></returns>
        public static string GetAllowedMessageMediaTypes(string allowedMediaTypes)
        {
            if (string.IsNullOrWhiteSpace(allowedMediaTypes))
            {
                return "all";
            }

            // Get the names of the media types from their IDs
            string mediaTypeNames = string.Empty;
            string[] mediaTypeIds = allowedMediaTypes.Split(',');
            foreach (string mediaType in mediaTypeIds)
            {
                umbraco.cms.businesslogic.media.MediaType mt = new umbraco.cms.businesslogic.media.MediaType(Convert.ToInt32(mediaType));
                mediaTypeNames += "," + mt.Text;
            }
            mediaTypeNames = mediaTypeNames.TrimStart(',').Replace(",", " | ");

            return mediaTypeNames;
        }

        /// <summary>
        /// Return a message stating you can only select a folder
        /// </summary>
        /// <param name="allowedExtension"></param>
        /// <returns></returns>
        public static string GetAllowedMessageFolder()
        {
            return string.Format("You can only select a folder.");
        }

        /// <summary>
        /// Return a message which extensions are allowed in the correct format.
        /// </summary>
        /// <param name="allowedExtension"></param>
        /// <returns></returns>
        public static string GetAllowedMessageStructure(XElement mediaElement, int selectedMediaType)
        {
            umbraco.cms.businesslogic.media.MediaType mtMedia = new umbraco.cms.businesslogic.media.MediaType(selectedMediaType);
            umbraco.cms.businesslogic.media.MediaType mtFolder = umbraco.cms.businesslogic.media.MediaType.GetByAlias(GetMediaType(mediaElement));

            return string.Format("You cannot create a media of the type {1} under a type of {0}.  Please select another Media Type or Location.", mtFolder.Text, mtMedia.Text);
        }

        #endregion

        #region Image/Thumbnail

        public static string ShowImage(string path, int desiredWidth, int desiredHeight, int nodeId, string crop = "", string icon = "", string cssClass = "", string id = "", string style = "")
        {

            string imgToShow = path;
            string ext = path.Substring(path.LastIndexOf(".") + 1);
            string extLow = ext.ToLower();

            if (DigibizConstants.IMAGETYPES.IndexOf("," + extLow + ",") < 0)
            {
                imgToShow = icon;
            }
            else if (!string.IsNullOrEmpty(crop))
            {
                if (DigibizConstants.IMAGEPREVIEW_INDICATECROPS)
                    cssClass += " cropped";

                imgToShow = crop;
            }

            // Generate img tag
            // Always add UniqueId so the image doesn't get cached.
            string imageTag = "<img src=\"/umbraco/plugins/DigibizAdvancedMediaPicker/DAMP_ImagePreview.ashx?img={0}&w={1}&h={2}&uId={8}&fallback={7}\" border=\"0\" class=\"{3}\" id=\"{4}\" style=\"{5}\" nodeId=\"{6}\">";
            imageTag = string.Format(imageTag,
                imgToShow,
                desiredWidth,
                desiredHeight,
                cssClass,
                id,
                style,
                nodeId == 0 ? "" : nodeId.ToString(),
                icon,
                GetUniqueId());

            return imageTag;


        }

        public static string GetUniqueId()
        {
            return DateTime.Now.Ticks.ToString();
        }

        public static string GetMediaTypeIcon(int mediaId)
        {
            Media m = new Media(mediaId);
            return umbraco.IO.SystemDirectories.Umbraco + "/images/thumbnails/" + m.ContentType.Thumbnail;
        }
        #endregion
    }
}
